import babelTraverse from '@babel/traverse';
import * as babelParser from '@babel/parser';
import * as vueTemplateCompiler from 'vue-template-compiler';
import { isExportDefaultDeclaration, isObjectExpression, isObjectProperty, isObjectMethod, CommentBlock, CommentLine, ExpressionStatement } from '@babel/types';

import store from '../../store';
import { IVueSfcParseResultTurnBack } from '../../types/vue-sfc';
import { readFileContent, writeFileContent, randomCode, isNotEmptyObject, getSpaceChars } from '../../utils';
import { splicCommentForVueProp, splicCommentForVueMethod, splicCommentForVueEvent, parseCommentForEventMarkDetails, splicCommentForVueEventDetails } from '../../comment';

import {
    isEmitEvent,
    getMethodName,
    findSlotNodes,
    addCommentHandle,
    getEmitEventInfo,
    getOptionTabSize,
    getObjectPropName,
    setDefaultValueType,
    upsetPropFieldValue,
    replaceCommentToFlag,
    formatTemplateContent
} from './utils';

export default turnBackDoc;

/**
 * 根据配置回写源码注释以及部分修改代码
 */
function turnBackDoc(filepath: string, config: IVueSfcParseResultTurnBack) {
    try {
        let code = readFileContent(filepath);
        if (code) {
            let component = vueTemplateCompiler.parseComponent(code);

            if (component.template) {
                const newTemplate = turnBackTemplateDoc(component.template, config);
                if (newTemplate) {
                    const { content, start, end } = newTemplate;
                    const startText = code.substring(0, start);
                    const endText = code.substring(end);
                    code = startText + formatTemplateContent(content) + endText;
                    // 更新 AST 解析信息
                    component = vueTemplateCompiler.parseComponent(code);
                }
            }

            if (component.script) {
                const newScript = turnBackScriptDoc(component.script, config);
                if (newScript) {
                    const { content, start, end } = newScript;
                    const startText = code.substring(0, start);
                    const endText = code.substring(end);
                    code = startText + content + endText;
                }
            }

            writeFileContent(filepath, code);
        }

        store.resetRandomCodeOneCharIndex();

        // ...
    } catch (error) {
        store.resetRandomCodeOneCharIndex();
        throw error;
    }
}

/**
 * 针对模板回写
 */
function turnBackTemplateDoc(template: vueTemplateCompiler.SFCBlock, configs: IVueSfcParseResultTurnBack) {
    if (configs.template) {
        // ...
        let templateContent = template.content;

        const templateAst = vueTemplateCompiler.compile(template.content, {
            // @ts-ignore
            comments: true,
            whitespace: 'condense'
        }).ast;

        if (templateContent && templateAst) {
            // ...
            // 源码内容替换字符串映射表
            // 为了确保 start 和 end 定位不会随着内容的变更而定位错误
            // 先使用同等长度的标记位替换需要变更的内容，并且收集起来，最后再统一变更内容
            const replaceStrMap: { [key: string]: { text: string } } = {};

            // 标识 Slot 节点注释
            if (configs?.template?.slots && isNotEmptyObject(configs.template.slots)) {
                const slotNodes = findSlotNodes(templateAst, templateContent);
                if (slotNodes.length) {
                    for (const slotNode of slotNodes) {
                        const config = configs.template.slots[slotNode.name];
                        if (config) {
                            // 修改注释
                            if (slotNode.comment) {
                                const { start, fullText } = slotNode.comment;
                                const key = randomCode(fullText.length);
                                const startText = templateContent.substring(0, start);
                                const endText = templateContent.substring(start).replace(fullText, key);
                                templateContent = startText + endText;
                                replaceStrMap[key] = { text: `<!-- ${config.desp} -->` };
                            } else {
                                // 添加注释
                                const { start, sourceText } = slotNode;
                                const key = randomCode(sourceText.length);
                                const startText = templateContent.substring(0, start);
                                const endText = templateContent.substring(start).replace(sourceText, key);
                                templateContent = startText + endText;
                                replaceStrMap[key] = { text: `<!-- ${config.desp} -->\n${sourceText}` };
                            }
                        }
                    }
                }
            }

            // 替换源码内容
            for (const key in replaceStrMap) {
                const reg = new RegExp(key);
                templateContent = templateContent.replace(reg, replaceStrMap[key].text);
            }

            const tmpStart = template.start || 0;
            const tmpEnd = template.end || tmpStart + templateContent.length;

            return {
                start: tmpStart,
                end: tmpEnd,
                content: templateContent
            };
        }
    }
}

/**
 * 针对脚本回写
 */
function turnBackScriptDoc(script: vueTemplateCompiler.SFCBlock, configs: IVueSfcParseResultTurnBack) {
    if (configs.script) {
        let scriptContent = script.content;
        const scriptAst = babelParser.parse(scriptContent, { sourceType: 'module' });

        if (scriptContent && scriptAst) {
            // ...
            // 源码内容替换字符串映射表
            // 为了确保 start 和 end 定位不会随着内容的变更而定位错误
            // 先使用同等长度的标记位替换需要变更的内容，并且收集起来，最后再统一变更内容
            const replaceStrMap: { [key: string]: { text: string; offset: number; operType: 'inset' | 'replace' } } = {};

            // 记录数据
            const store: {
                events: {
                    [eventName: string]: {
                        replaceKey?: string;
                        hasTurnBack: boolean;
                        node: CommentBlock | CommentLine | ExpressionStatement;
                    };
                };
            } = {
                events: {}
            };

            // 回写全局注释
            if (scriptAst.comments) {
                for (const comment of scriptAst.comments) {
                    // ...
                    // 回写 Event
                    const events = parseCommentForEventMarkDetails(scriptContent, comment);
                    if (events.length) {
                        events.forEach(event => {
                            // 只修改注释，不添加
                            if (event.name && configs.script?.events?.[event.name]) {
                                const { start, end } = event;
                                const startText = scriptContent.substring(0, start);
                                const endText = scriptContent.substring(end);
                                const value = scriptContent.substring(start, end);
                                const newValue = splicCommentForVueEventDetails(event.name, configs.script.events[event.name]);

                                const replaceKey = randomCode(value.length);
                                scriptContent = startText + replaceKey + endText;

                                replaceStrMap[replaceKey] = { text: newValue, offset: start, operType: 'replace' };
                                store.events[event.name] = { hasTurnBack: true, node: comment, replaceKey };
                            }
                        });
                    }
                }
            }

            babelTraverse(scriptAst, {
                ExportDefaultDeclaration(nodePath) {
                    let exportDefaultNode = null;
                    if (Array.isArray(nodePath.container)) {
                        exportDefaultNode = nodePath.container.find(f => {
                            return isExportDefaultDeclaration(f);
                        });
                    } else if (isExportDefaultDeclaration(nodePath.container)) {
                        exportDefaultNode = nodePath.container;
                    }
                    if (!isExportDefaultDeclaration(exportDefaultNode)) return;

                    // @ts-ignore
                    const options = exportDefaultNode.declaration.properties;
                    const optionTabSize = getOptionTabSize(scriptContent, options);

                    for (const option of options) {
                        const optionName = option.key.name;
                        const optionValue = option.value;

                        switch (optionName) {
                            case 'props': {
                                if (configs.script?.props && isObjectExpression(optionValue)) {
                                    for (const prop of optionValue.properties) {
                                        if (isObjectProperty(prop)) {
                                            const propName = getObjectPropName(prop);
                                            if (propName && configs.script.props[propName]) {
                                                const hasComment = replaceCommentToFlag(scriptContent, prop);
                                                if (hasComment) {
                                                    // 修改注释
                                                    const { key, code, spaceLen } = hasComment;
                                                    scriptContent = code;
                                                    replaceStrMap[key] = { text: splicCommentForVueProp(configs.script.props[propName], { spaceLen }), offset: hasComment.start, operType: 'replace' };
                                                } else {
                                                    // 添加注释
                                                    const { insetOffset, spaceLen, key } = addCommentHandle(scriptContent, prop);
                                                    const newCommentText = splicCommentForVueProp(configs.script.props[propName], { spaceLen });
                                                    replaceStrMap[key] = { text: newCommentText + '\n' + getSpaceChars(spaceLen), offset: insetOffset, operType: 'inset' };
                                                }

                                                // 处理 default 字段
                                                if ('default' in configs.script.props[propName]) {
                                                    setDefaultValueType(configs.script.props[propName]);
                                                    const ret = upsetPropFieldValue({
                                                        nodePath: prop,
                                                        code: scriptContent,
                                                        prop: configs.script.props[propName],
                                                        keyName: 'default',
                                                        replaceStrMap,
                                                        optionTabSize
                                                    });
                                                    scriptContent = ret.code;
                                                }

                                                // 处理 required 字段
                                                if ('required' in configs.script.props[propName]) {
                                                    const ret = upsetPropFieldValue({
                                                        nodePath: prop,
                                                        code: scriptContent,
                                                        prop: configs.script.props[propName],
                                                        keyName: 'required',
                                                        replaceStrMap,
                                                        optionTabSize
                                                    });
                                                    scriptContent = ret.code;
                                                }

                                                // 处理 type 字段
                                                if ('type' in configs.script.props[propName]) {
                                                    const ret = upsetPropFieldValue({
                                                        nodePath: prop,
                                                        code: scriptContent,
                                                        prop: configs.script.props[propName],
                                                        keyName: 'type',
                                                        replaceStrMap,
                                                        optionTabSize
                                                    });
                                                    scriptContent = ret.code;
                                                }
                                            }
                                        }
                                    }
                                }
                            }

                            case 'methods': {
                                if (configs.script?.methods && isObjectExpression(optionValue)) {
                                    for (const method of optionValue.properties) {
                                        if (isObjectMethod(method)) {
                                            const methodName = getMethodName(method);
                                            if (methodName && configs.script.methods[methodName]) {
                                                const hasComment = replaceCommentToFlag(scriptContent, method);
                                                if (hasComment) {
                                                    // 修改注释
                                                    const { key, code, spaceLen } = hasComment;
                                                    scriptContent = code;
                                                    replaceStrMap[key] = {
                                                        text: splicCommentForVueMethod(configs.script.methods[methodName], { spaceLen }),
                                                        offset: hasComment.start,
                                                        operType: 'replace'
                                                    };
                                                } else {
                                                    // 添加注释
                                                    const { insetOffset, spaceLen, key } = addCommentHandle(scriptContent, method);
                                                    const newCommentText = splicCommentForVueMethod(configs.script.methods[methodName], { spaceLen });
                                                    replaceStrMap[key] = { text: newCommentText + '\n' + getSpaceChars(spaceLen), offset: insetOffset, operType: 'inset' };
                                                }
                                            }
                                        }
                                    }
                                }
                                break;
                            }
                        }
                    }
                },

                ExpressionStatement(nodePath) {
                    const statement = nodePath.node;

                    // 回写 $emit 语句的注释
                    if (configs.script?.events && isEmitEvent(statement)) {
                        const { event: eventName } = getEmitEventInfo(statement) || {};
                        if (eventName && configs.script.events[eventName] && !store.events[eventName]?.hasTurnBack) {
                            const hasComment = replaceCommentToFlag(scriptContent, statement);
                            if (hasComment) {
                                // 修改注释
                                const { key, code, spaceLen } = hasComment;
                                scriptContent = code;
                                replaceStrMap[key] = { text: splicCommentForVueEvent(configs.script.events[eventName], { spaceLen }), offset: hasComment.start, operType: 'replace' };
                            } else {
                                // 添加注释
                                const { insetOffset, spaceLen, key } = addCommentHandle(scriptContent, statement);
                                const newCommentText = splicCommentForVueEvent(configs.script.events[eventName], { spaceLen });
                                replaceStrMap[key] = { text: newCommentText + '\n' + getSpaceChars(spaceLen), offset: insetOffset, operType: 'inset' };
                            }
                            store.events[eventName] = { hasTurnBack: true, node: nodePath.node };
                        }
                    }
                }
            });

            // 替换源码内容
            let addTextLen = 0;
            const insetArray = [];
            const replaceArray = [];
            for (const key in replaceStrMap) {
                const item = replaceStrMap[key];
                item.operType === 'inset' ? insetArray.push(item) : replaceArray.push({ ...item, key });
            }
            insetArray.forEach(item => {
                const insetOffset = item.offset + addTextLen;
                const startText = scriptContent.substring(0, insetOffset);
                const endText = scriptContent.substring(insetOffset);
                scriptContent = startText + item.text + endText;
                addTextLen += item.text.length;
            });
            replaceArray.forEach(item => {
                const reg = new RegExp(item.key);
                const text = replaceStrMap[item.key].text;
                scriptContent = scriptContent.replace(reg, text);
            });

            const scriptStart = script.start || 0;
            const scriptEnd = script.end || scriptStart + scriptContent.length;

            return {
                start: scriptStart,
                end: scriptEnd,
                content: scriptContent
            };
        }
    }
}
